﻿// ------------------------------------------------------------------------
// 版权信息
// 版权归重庆虫儿飞科技有限公司所有。
// 所有权利保留。
// 官方网站：https://netokit.com
// 许可证信息
// Neto.Kit 项目主要遵循 MIT 许可证和 Apache 许可证（版本 2.0）进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// 
// 使用条款
// 使用本代码应遵守相关法律法规和许可证的要求。
// 
// 免责声明
// 对于因使用本代码而产生的任何直接、间接、偶然、特殊或后果性损害，我们不承担任何责任。
// 
// 其他重要信息
// Neto.Kit 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。
// 有关 Neto.Kit 项目的其他详细信息，请参阅位于源代码树根目录中的 COPYRIGHT 和 DISCLAIMER 文件。
// 
// 更多信息
// 请访问 https://netokit.com 获取更多关于 Neto.Kit 项目的许可证和版权信息。
// ------------------------------------------------------------------------

namespace Neto.Files;

/// <summary>
///     文件头验证
/// </summary>
public static class FileHeaderCheckHelper
{
    // 使用字典存储文件类型和对应的文件头字节模式，允许一个文件类型有多个签名
    private static readonly Dictionary<string, List<byte[]>> FileHeaderSignatures =
        new()
        {
            // 图像文件
            { ".png", [new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }] }, //【已确认】
            { ".jpg", [new byte[] { 0xFF, 0xD8 }] }, //【已确认】
            { ".jpeg", [new byte[] { 0xFF, 0xD8 }] }, //【已确认】
            {
                ".gif", [
                    new byte[] { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 } // GIF87a
                    ,
                    new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 } // GIF89a
                ]
            }, //【已确认】

            { ".bmp", [new byte[] { 0x42, 0x4D }] }, ////【已确认】

            // PDF 文件
            { ".pdf", [new byte[] { 0x25, 0x50, 0x44, 0x46 }] }, //【已确认】 %PDF

            // Office 文件
            { ".doc", [new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 }] }, //【已确认】
            { ".xls", [new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 }] }, //【已确认】
            { ".ppt", [new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 }] }, //【已确认】
            { ".docx", [new byte[] { 0x50, 0x4B, 0x03, 0x04 }] }, //【已确认】
            { ".xlsx", [new byte[] { 0x50, 0x4B, 0x03, 0x04 }] }, //【已确认】
            { ".pptx", [new byte[] { 0x50, 0x4B, 0x03, 0x04 }] }, //【已确认】
            //{ ".docm", [new byte[] { 0x50, 0x4B, 0x03, 0x04 }] }, //【已确认】
            { ".rtf", [new byte[] { 0x7B, 0x5C, 0x72, 0x74, 0x66, 0x31 }] }, //【已确认】
            // 压缩文件
            {
                ".zip", [
                    new byte[] { 0x50, 0x4B, 0x03, 0x04 }, new byte[] { 0x50, 0x4B, 0x05, 0x06 },
                    new byte[] { 0x50, 0x4B, 0x07, 0x08 }
                ]
            }, //【已确认】
            { ".7z", [new byte[] { 0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C }] }, //【已确认】
            //{ ".tar", [new byte[] { 0x75, 0x73, 0x74, 0x61, 0x72, 0x00 }] }, //注意：实际的 .tar 头部会包含多个00和其他信息
            { ".gz", [new byte[] { 0x1F, 0x8B }] }, //【已确认】
            { ".gzip", [new byte[] { 0x1F, 0x8B }] }, //【已确认】
            { ".bz2", [new byte[] { 0x42, 0x5A, 0x68 }] }, //【已确认】
            { ".bzip2", [new byte[] { 0x42, 0x5A, 0x68 }] }, //【已确认】
            //{ ".Z", [new byte[] { 0x1F, 0x9D }] },
            {
                ".rar", [
                    new byte[] { 0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00 },
                    new byte[] { 0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x01, 0x00 }
                ]
            }, //【已确认】

            // 视频/音频
            //.avi 单独验证
            { ".m4a", [new byte[] { 0x46, 0x4F, 0x52, 0x4D }] },
            //{ ".mp3", [new byte[] { 0xFF, 0xFB }] }, //【已确认】
            //.mp4 单独验证
            { ".webm", [new byte[] { 0x1A, 0x45, 0xDF, 0xA3 }] }, //【已确认】【已验证】
            { ".flv", [new byte[] { 0x46, 0x4C, 0x56, 0x01 }] }, //【已确认】【已验证】
            //.mov 单独验证
            { ".rm", [new byte[] { 0x2E, 0x52, 0x4D, 0x46 }] }, //【已验证】
            { ".rmvb", [new byte[] { 0x2E, 0x52, 0x4D, 0x46 }] }, //【待确认】
            //.wmv 单独验证
            //{ ".mpeg", [new byte[] { 0x00, 0x00, 0x01,0xBA },new byte[]{0x47, 0x00, 0x00, 0x01}] }, // 
            //{ ".div", [new byte[] { 0x00, 0x00, 0x01, 0xBA }] },
            //{ ".mpg", [new byte[] { 0x00, 0x00, 0x01 }] },
            //{ ".mpe", [new byte[] { 0x00, 0x00, 0x01 }] },

            // 其他文件
            { ".bat", [new byte[] { 0x4D, 0x5A }] },
            { ".txt", [new byte[] { 0x00, 0x00, 0x00, 0x00 }] },
            { ".csv", [new byte[] { 0x00, 0x00, 0x00, 0x00 }] },
            { ".ini", [new byte[] { 0x00, 0x00, 0x00, 0x00 }] },
            { ".htm", [new byte[] { 0x00, 0x00, 0x00, 0x00 }] },
            { ".html", [new byte[] { 0x00, 0x00, 0x00, 0x00 }] }
        };

    /// <summary>
    ///     检查文件头
    /// </summary>
    /// <param name="filePath">文件完整路径</param>
    /// <returns></returns>
    public static FileHeaderCheckResult IsFileHeaderValid(string filePath)
    {
        //根据文件地址，把文件流读取出来,不要独占
        var fileStream = File.OpenRead(filePath);

        var ext = Path.GetExtension(filePath);

        return IsFileHeaderValid(fileStream, ext);
    }

    /// <summary>
    ///     检查文件头
    /// </summary>
    /// <param name="fileStream"></param>
    /// <param name="ext">文件扩展名，带点，例如：.webp</param>
    /// <returns></returns>
    public static FileHeaderCheckResult IsFileHeaderValid(Stream? fileStream, string ext)
    {
        var result = new FileHeaderCheckResult();

        if (fileStream == null || fileStream.Length < 1)
        {
            result.Error = "文件流为空或不存在。";
            result.State = FileHeaderCheckState.错误;
            return result;
        }


        try
        {
            //.webp 
            if (string.Equals(ext, ".webp", StringComparison.CurrentCultureIgnoreCase))
            {
                var headerBytes = ReadBytes(fileStream, 20);
                // WEBP文件以'RIFF'开头（0x52 0x49 0x46 0x46），之后是文件大小，然后是'WEBP'（0x57 0x45 0x42 0x50）
                result.IsSuccess = headerBytes[0] == 0x52 && headerBytes[1] == 0x49 && headerBytes[2] == 0x46 &&
                                   headerBytes[3] == 0x46 &&
                                   headerBytes[8] == 0x57 && headerBytes[9] == 0x45 && headerBytes[10] == 0x42 &&
                                   headerBytes[11] == 0x50;
            }
            //.avi
            else if (string.Equals(ext, ".avi", StringComparison.CurrentCultureIgnoreCase))
            {
                var headerBytes = ReadBytes(fileStream, 20);

                // WEBP文件以'RIFF'开头（0x52 0x49 0x46 0x46），紧接着是文件大小（4字节，小端序），然后是文件类型标识AVI（注意末尾有个空格，ASCII值分别为0x41, 0x56, 0x49, 0x20）
                result.IsSuccess = headerBytes[0] == 0x52 && headerBytes[1] == 0x49 && headerBytes[2] == 0x46 &&
                                   headerBytes[3] == 0x46 &&
                                   headerBytes[8] == 0x41 && headerBytes[9] == 0x56 && headerBytes[10] == 0x49 &&
                                   headerBytes[11] == 0x20;
            }
            //.mp4
            else if (string.Equals(ext, ".mp4", StringComparison.CurrentCultureIgnoreCase))
            {
                //并读取前12个字节。然后，它将这些字节转换为字符串，并检查字符串是否包含"ftyp"。如果包含，那么它将检查"ftyp"后的四个字符是否是"mp4 "，"isom"，"iso2"或者"avc1"。如果是，那么它将返回true，表示这是一个.mp4文件。否则，它将返回false。
                var headerBytes = ReadBytes(fileStream, 12);
                var header = Encoding.UTF8.GetString(headerBytes);
                if (header.Contains("ftyp"))
                {
                    var brand = header.Substring(header.IndexOf("ftyp") + 4, 4);
                    if (brand == "mp4 " || brand == "isom" || brand == "iso2" || brand == "avc1")
                        result.IsSuccess = true;
                }
            }
            //.mov
            else if (string.Equals(ext, ".mov", StringComparison.CurrentCultureIgnoreCase))
            {
                // 检查前4字节是否为atom size，对于验证目的，我们更关心下一个4字节的atom type
                // 第5至第8字节应为'ftype'
                var headerBytes = ReadBytes(fileStream, 8);

                if (headerBytes[4] == 0x66 && headerBytes[5] == 0x74 && headerBytes[6] == 0x79 &&
                    headerBytes[7] == 0x70)
                    result.IsSuccess = true;
            }
            //.wmv
            else if (string.Equals(ext, ".wmv", StringComparison.CurrentCultureIgnoreCase))
            {
                //魔数头
                var signatues = new byte[] { 0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9, 0x00, 0xAA };

                //读取签名长度的魔数头
                var headerBytes = ReadBytes(fileStream, signatues.Length);

                result.IsSuccess = headerBytes.SequenceEqual(signatues);
            }
            //.mpeg、.mpe、.mpg【未验证】
            else if (new List<string> { ".mpeg", ".mpe", ".mpg" }.Contains(ext.ToLower()))
            {
                // 检查前4字节是否为atom size，对于验证目的，我们更关心下一个4字节的atom type
                // 第5至第8字节应为'ftype'
                //{ 0x00, 0x00, 0x01,0xBA },{0x47, 0x00, 0x00, 0x01}
                var headerBytes = ReadBytes(fileStream, 4);
                if (headerBytes[0] == 0x00 && headerBytes[1] == 0x00 && headerBytes[2] == 0x01 &&
                    headerBytes[3] == 0xBA)
                    result.IsSuccess = true;
                else if (headerBytes[0] == 0x47 && headerBytes[1] == 0x00 && headerBytes[2] == 0x00 &&
                         headerBytes[3] == 0x00)
                    result.IsSuccess = true;
            }
            //.mkv【已确认】【已验证】
            else if (new List<string> { ".mkv" }.Contains(ext.ToLower()))
            {
                //MKV（Matroska）文件的魔数（Magic Number）文件头是 1A 45 DF A3.
                //这四个字节在文件最开始处，用于标识该文件是一个MKV格式的文件。这种魔数是固定不变的，
                //用于快速、准确地判断文件类型，即使文件扩展名被改变也能识别其真实格式
                //1A 45 DF A3
                var headerBytes = ReadBytes(fileStream, 4);

                if (headerBytes[0] == 0x1A && headerBytes[1] == 0x45 && headerBytes[2] == 0xDF &&
                    headerBytes[3] == 0xA3)
                    result.IsSuccess = true;
            }
            // else if ((new List<string>() { ".mp3" }).Contains(ext.ToLower()))
            // {
            //     var headerBytes = ReadBytes(fileStream, 50);
            // }
            else
            {
                if (!FileHeaderSignatures.TryGetValue(ext.ToLower(), out var signatures))
                {
                    result.Error = "未找到指定文件扩展名的签名。";
                    result.State = FileHeaderCheckState.未找到验证规则;
                    return result;
                }

                var isValid = false;
                foreach (var signature in signatures)
                {
                    // 根据文件扩展名检查文件头
                    var header = ReadBytes(fileStream, signature.Length);
                    // 检查读取的字节长度是否符合预期
                    if (header.Length < signature.Length)
                        continue;

                    isValid = header.SequenceEqual(signature);
                    if (isValid) break;
                }

                result.IsSuccess = isValid;
            }
        }
        catch (IOException ioException)
        {
            Console.WriteLine($"读取文件流时发生错误: {ioException.Message}");
            result.Error = $"读取文件流时发生错误: {ioException.Message}";
            result.State = FileHeaderCheckState.错误;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"检查文件头时发生意外错误: {ex.Message}");
            result.Error = $"读取文件流时发生错误: {ex.Message}";
            result.State = FileHeaderCheckState.错误;
        }
        finally
        {
            if (result.IsSuccess)
                result.State = FileHeaderCheckState.成功;
            else
                result.State = FileHeaderCheckState.失败;
        }

        return result;
    }

    /// <summary>
    ///     上传文件头检查
    /// </summary>
    /// <param name="fileStream"></param>
    /// <param name="ext"></param>
    /// <returns></returns>
    public static FileHeaderCheckResult UploadFileHeaderCheckResult(Stream? fileStream, string ext)
    {
        var result = new FileHeaderCheckResult();

        if (fileStream == null || fileStream.Length < 1)
        {
            result.Error = "文件流为空或不存在。";
            result.State = FileHeaderCheckState.错误;
            return result;
        }


        try
        {
            //.webp 
            if (string.Equals(ext, ".webp", StringComparison.CurrentCultureIgnoreCase))
            {
                var headerBytes = ReadBytes(fileStream, 20);
                // WEBP文件以'RIFF'开头（0x52 0x49 0x46 0x46），之后是文件大小，然后是'WEBP'（0x57 0x45 0x42 0x50）
                result.IsSuccess = headerBytes[0] == 0x52 && headerBytes[1] == 0x49 && headerBytes[2] == 0x46 &&
                                   headerBytes[3] == 0x46 &&
                                   headerBytes[8] == 0x57 && headerBytes[9] == 0x45 && headerBytes[10] == 0x42 &&
                                   headerBytes[11] == 0x50;
            }
            //.avi
            else if (string.Equals(ext, ".avi", StringComparison.CurrentCultureIgnoreCase))
            {
                var headerBytes = ReadBytes(fileStream, 20);

                // WEBP文件以'RIFF'开头（0x52 0x49 0x46 0x46），紧接着是文件大小（4字节，小端序），然后是文件类型标识AVI（注意末尾有个空格，ASCII值分别为0x41, 0x56, 0x49, 0x20）
                result.IsSuccess = headerBytes[0] == 0x52 && headerBytes[1] == 0x49 && headerBytes[2] == 0x46 &&
                                   headerBytes[3] == 0x46 &&
                                   headerBytes[8] == 0x41 && headerBytes[9] == 0x56 && headerBytes[10] == 0x49 &&
                                   headerBytes[11] == 0x20;
            }
            //.mp4
            else if (string.Equals(ext, ".mp4", StringComparison.CurrentCultureIgnoreCase))
            {
                //并读取前12个字节。然后，它将这些字节转换为字符串，并检查字符串是否包含"ftyp"。如果包含，那么它将检查"ftyp"后的四个字符是否是"mp4 "，"isom"，"iso2"或者"avc1"。如果是，那么它将返回true，表示这是一个.mp4文件。否则，它将返回false。
                var headerBytes = ReadBytes(fileStream, 12);
                var header = Encoding.UTF8.GetString(headerBytes);
                if (header.Contains("ftyp"))
                {
                    var brand = header.Substring(header.IndexOf("ftyp") + 4, 4);
                    if (brand == "mp4 " || brand == "isom" || brand == "iso2" || brand == "avc1")
                        result.IsSuccess = true;
                }
            }
            //.mov
            else if (string.Equals(ext, ".mov", StringComparison.CurrentCultureIgnoreCase))
            {
                // 检查前4字节是否为atom size，对于验证目的，我们更关心下一个4字节的atom type
                // 第5至第8字节应为'ftype'
                var headerBytes = ReadBytes(fileStream, 8);

                if (headerBytes[4] == 0x66 && headerBytes[5] == 0x74 && headerBytes[6] == 0x79 &&
                    headerBytes[7] == 0x70)
                    result.IsSuccess = true;
            }
            //.wmv
            else if (string.Equals(ext, ".wmv", StringComparison.CurrentCultureIgnoreCase))
            {
                //魔数头
                var signatues = new byte[] { 0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9, 0x00, 0xAA };

                //读取签名长度的魔数头
                var headerBytes = ReadBytes(fileStream, signatues.Length);

                result.IsSuccess = headerBytes.SequenceEqual(signatues);
            }
            //.mpeg、.mpe、.mpg【未验证】
            else if (new List<string> { ".mpeg", ".mpe", ".mpg" }.Contains(ext.ToLower()))
            {
                // 检查前4字节是否为atom size，对于验证目的，我们更关心下一个4字节的atom type
                // 第5至第8字节应为'ftype'
                //{ 0x00, 0x00, 0x01,0xBA },{0x47, 0x00, 0x00, 0x01}
                var headerBytes = ReadBytes(fileStream, 4);
                if (headerBytes[0] == 0x00 && headerBytes[1] == 0x00 && headerBytes[2] == 0x01 &&
                    headerBytes[3] == 0xBA)
                    result.IsSuccess = true;
                else if (headerBytes[0] == 0x47 && headerBytes[1] == 0x00 && headerBytes[2] == 0x00 &&
                         headerBytes[3] == 0x00)
                    result.IsSuccess = true;
            }
            //.mkv【已确认】【已验证】
            else if (new List<string> { ".mkv" }.Contains(ext.ToLower()))
            {
                //MKV（Matroska）文件的魔数（Magic Number）文件头是 1A 45 DF A3.
                //这四个字节在文件最开始处，用于标识该文件是一个MKV格式的文件。这种魔数是固定不变的，
                //用于快速、准确地判断文件类型，即使文件扩展名被改变也能识别其真实格式
                //1A 45 DF A3
                var headerBytes = ReadBytes(fileStream, 4);

                if (headerBytes[0] == 0x1A && headerBytes[1] == 0x45 && headerBytes[2] == 0xDF &&
                    headerBytes[3] == 0xA3)
                    result.IsSuccess = true;
            }
            // else if ((new List<string>() { ".mp3" }).Contains(ext.ToLower()))
            // {
            //     var headerBytes = ReadBytes(fileStream, 50);
            // }
            //单独处理png、jpg、jpeg,因为扩展名不一定准确
            else if (new List<string> { ".png", ".jpg", ".jpeg" }.Contains(ext.ToLower()))
            {
                //png:0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A
                //jpg\jpeg:0xFF, 0xD8
                var headerBytes = new List<byte[]>
                {
                    new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A } //PNG的文件头
                    ,
                    new byte[] { 0xFF, 0xD8 } //JPEG/JPG的文件头
                };
                foreach (var isValid in from signature in headerBytes
                         let header = ReadBytes(fileStream, signature.Length)
                         where header.Length >= signature.Length
                         select header.SequenceEqual(signature)
                         into isValid
                         where isValid
                         select isValid)
                {
                    result.IsSuccess = true;
                    break;
                }
            }
            else
            {
                if (!FileHeaderSignatures.TryGetValue(ext.ToLower(), out var signatures))
                {
                    result.Error = "未找到指定文件扩展名的签名。";
                    result.State = FileHeaderCheckState.未找到验证规则;
                    return result;
                }

                var isValid = false;
                foreach (var signature in signatures)
                {
                    // 根据文件扩展名检查文件头
                    var header = ReadBytes(fileStream, signature.Length);
                    // 检查读取的字节长度是否符合预期
                    if (header.Length < signature.Length)
                        continue;

                    isValid = header.SequenceEqual(signature);
                    if (isValid) break;
                }

                result.IsSuccess = isValid;
            }
        }
        catch (IOException ioException)
        {
            Console.WriteLine($"读取文件流时发生错误: {ioException.Message}");
            result.Error = $"读取文件流时发生错误: {ioException.Message}";
            result.State = FileHeaderCheckState.错误;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"检查文件头时发生意外错误: {ex.Message}");
            result.Error = $"读取文件流时发生错误: {ex.Message}";
            result.State = FileHeaderCheckState.错误;
        }
        finally
        {
            if (result.IsSuccess)
                result.State = FileHeaderCheckState.成功;
            else
                result.State = FileHeaderCheckState.失败;
        }

        return result;
    }

    /// <exception cref="IOException">读取文件时发生错误</exception>
    /// <summary>
    ///     从指定的流中读取固定长度的字节数组。
    /// </summary>
    /// <param name="fileStream">要读取的流对象，不应为null。</param>
    /// <param name="readLength">要读取的字节数，必须为非负数。</param>
    /// <returns>包含已读取字节的字节数组。</returns>
    public static byte[] ReadBytes(Stream fileStream, int readLength)
    {
        // 检查fileStream是否为null，如果是，则抛出ArgumentNullException异常。
        ArgumentNullException.ThrowIfNull(fileStream);

        // 检查readLength是否为负数，如果是，则抛出ArgumentOutOfRangeException异常。
        ArgumentOutOfRangeException.ThrowIfNegative(readLength);

        // 初始化一个长度为readLength的字节数组，用于存储读取的字节。
        var headerBytes = new byte[readLength];

        try
        {
            // 将文件流的位置设置为开始，确保每次读取都从流的起始位置开始。
            fileStream.Seek(0, SeekOrigin.Begin);

            // 初始化已读取的总字节数为0。
            var totalBytesRead = 0;
            // 初始化还需要读取的字节数。
            var bytesToRead = readLength;

            // 当已读取的总字节数小于预期长度时，继续读取。
            while (totalBytesRead < readLength)
            {
                // 读取字节到headerBytes数组中，从totalBytesRead位置开始读取bytesToRead个字节。
                var bytesRead = fileStream.Read(headerBytes, totalBytesRead, bytesToRead);
                // 如果读取到的字节数为0，表示已达到文件末尾，退出循环。
                if (bytesRead == 0) break;

                // 更新已读取的总字节数。
                totalBytesRead += bytesRead;
                // 更新还需要读取的字节数。
                bytesToRead -= bytesRead;
            }

            // 如果已读取的总字节数不等于预期长度，抛出IOException异常。
            if (totalBytesRead != readLength) throw new IOException("Failed to read the requested number of bytes.");
#if DEBUG
            var str = Encoding.UTF8.GetString(headerBytes);
#endif
        }
        catch (IOException ex)
        {
            // 如果在读取过程中发生IOException异常，记录异常（根据需要），这里简单地重新抛出。
            throw new IOException("Error reading file", ex);
        }

        // 返回包含已读取字节的字节数组。
        return headerBytes;
    }


    /// <summary>
    ///     读取文件的头部字节
    /// </summary>
    /// <param name="filePath"></param>
    /// <param name="readLenght"></param>
    /// <returns></returns>
    public static byte[] RedFileHeaderBytes(string filePath, int readLenght)
    {
        if (!File.Exists(filePath))
            throw new FileNotFoundException("文件不存在");
        var fileStream = new FileStream(filePath, FileMode.Open);
        return ReadBytes(fileStream, readLenght);
    }

    /// <summary>
    ///     判断是否是docx或docm文件
    /// </summary>
    /// <param name="fileStream"></param>
    /// <returns></returns>
    private static bool IsDocxOrDocmHeaderValid(Stream fileStream)
    {
        var header = new byte[20];
        fileStream.ReadExactly(header, 0, 20);
        return header.SequenceEqual(new byte[]
        {
            0x50, 0x4B, 0x03, 0x04, 0x14, 0x00, 0x06, 0x00, 0x08, 0x00, 0x00, 0x00, 0x21, 0x00, 0x9B, 0x05, 0x6E,
            0x6F, 0x74, 0x65
        });
    }
}